<?php
// +----------------------------------------------------------------------
// | KITEGO-Admin「开箱即用」「人人全栈」
// +----------------------------------------------------------------------
// | Copyright (c) 2016 https://www.kitego.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed KITEGO并不是自由软件，未经许可不能去掉KITEGO相关版权
// +----------------------------------------------------------------------
// | Author: KITEGO Team <bd@kitego.cn>
// +----------------------------------------------------------------------

namespace kitego\utils;

use think\Model;

class DbLockUtil
{
    // 全局默认配置
    private static array $config = [
        'optimistic_retry' => 3, // 乐观锁默认重试次数
        'pessimistic_timeout' => 5, // 悲观锁超时时间(秒)
        'hybrid_switch_threshold' => 2, // 混合锁切换阈值
        'retry_delay' => [10, 50, 100] // 重试延迟(毫秒)，指数退避
    ];

    /**
     * 设置配置
     */
    public static function config(array $config = [])
    {
        self::$config = array_merge(self::$config, $config);
    }

    /**
     * 乐观锁更新（带表名日志）
     * @param Model $model 数据模型（需含version字段）
     * @param int $id 记录ID
     * @param callable $updateCallback 回调返回更新字段数组
     * @param int|null $retry 重试次数（null则用全局配置）
     */
    public static function optimisticLock(Model $model, int $id, callable $updateCallback, int $retry = null)
    {
        $retry = $retry ?? self::$config['optimistic_retry'];
        $currentRetry = 0;
        $tableName = $model->getTable();

        // 获取当前记录
        $currentModel = $model::where('id', $id)->find();
        if (!$currentModel) {
            self::log($tableName, $id, '乐观锁操作失败', "记录不存在");
            return false;
        }

        while ($currentRetry < $retry) {
            $originalVersion = $currentModel->version;
            $updateData = $updateCallback(clone $currentModel);

            if (empty($updateData)) {
                exception("更新数据不能为空");
            }

            if (isset($updateData['version']) && $updateData['version'] <= 0) {
                exception("version必须为正数");
            }

            // 强制版本号递增
            $updateData['version'] = $originalVersion + 1;

            // 执行更新（仅匹配当前版本）
            $where[] = ['id', '=', $id];
            $where[] = ['version', '=', $originalVersion];
            $result = $model::where($where)->update($updateData);

            if ($result > 0) {
                // 返回更新后的记录
                self::log(
                    $tableName,
                    $id,
                    '乐观锁更新成功',
                    "版本: {$originalVersion}→{$updateData['version']}"
                );

                // 直接返回更新后的数据
                return $updateData;
            }

            // 冲突重试（带指数退避延迟）
            $currentRetry++;

            $delay = self::$config['retry_delay'][$currentRetry - 1] ?? end(self::$config['retry_delay']);
            usleep($delay * 1000); // 毫秒转微秒

            $currentModel->refresh(); // 刷新最新数据

            self::log(
                $tableName,
                $id,
                '乐观锁冲突重试',
                "重试次数: {$currentRetry}/{$retry}, 延迟: {$delay}ms"
            );
        }

        self::log(
            $tableName,
            $id,
            '乐观锁重试耗尽',
            "总重试: {$retry}次"
        );

        return false;
    }

    /**
     * 悲观锁操作（带表名日志）
     * @param Model $model 模型类
     * @param int $id 记录ID
     * @param callable $operateCallback 操作回调
     * @param int|null $timeout 超时时间(秒)
     */
    public static function pessimisticLock(Model $model, int $id, callable $operateCallback, int $timeout = null)
    {
        $timeout = $timeout ?? self::$config['pessimistic_timeout'];
        $startTime = microtime(true);

        // 实例化模型获取表名（不查询数据库）
        $tableName = $model->getTable();

        return db_transaction(function () use (
            $model, $id, $operateCallback,
            $startTime, $timeout, $tableName
        ) {
            // 1. 检查锁超时（避免长事务）
            if ((microtime(true) - $startTime) > $timeout) {
                self::log(
                    $tableName,
                    $id,
                    '悲观锁超时',
                    "超时时间: {$timeout}秒"
                );

                exception("悲观锁超时（{$timeout}秒）");
            }

            // 2. 细粒度行锁（仅锁定目标记录）
            $lockedModel = $model::where('id', $id)->lock(true)->find();
            if (!$lockedModel) {
                self::log(
                    $tableName,
                    $id,
                    '悲观锁操作失败',
                    "记录不存在"
                );

                exception("记录不存在: {$tableName}#{$id}");
            }

            // 3. 执行操作（获取更新数据）
            $updateData = $operateCallback($model);

            if (empty($updateData)) {
                self::log(
                    $tableName,
                    $id,
                    '悲观锁操作中断',
                    "更新数据为空"
                );

                return false;
            }

            // 应用更新
            foreach ($updateData as $field => $value) {
                $model->$field = $value;
            }

            $model->save();

            self::log(
                $tableName,
                $id,
                '悲观锁操作成功',
                "耗时: " . round(microtime(true) - $startTime, 4) . "秒"
            );

            // 返回更新后的记录
            $updatedModel = $model::where(['id' => $id])->find();

            return $updatedModel->toArray();
        });
    }

    /**
     * 混合锁（带表名日志）
     * @param Model $model 模型实例
     * @param int $id 记录ID
     * @param callable $updateCallback 更新回调
     * @param int|null $switchThreshold 切换阈值
     */
    public static function hybridLock(Model $model, int $id, callable $updateCallback, int $switchThreshold = null)
    {
        $switchThreshold = $switchThreshold ?? self::$config['hybrid_switch_threshold'];
        $tableName = $model->getTable();

        // 1. 先尝试乐观锁（轻量操作）
        $result = self::optimisticLock($model, $id, $updateCallback, $switchThreshold);
        if ($result) {
            return $result;
        }

        // 2. 乐观锁冲突频繁，切换为悲观锁（保证最终一致性）
        self::log(
            $tableName,
            $id,
            '混合锁触发切换',
            "乐观锁重试{$switchThreshold}次失败，切换至悲观锁"
        );

        return self::pessimisticLock(
            $model,
            $id,
            function ($lockedModel) use ($updateCallback) {
                // 使用从数据库锁定的模型作为基准
                $updateData = $updateCallback(clone $lockedModel);

                // 过滤掉version字段，悲观锁无需手动维护
                foreach ($updateData as $field => $value) {
                    if ($field === 'version') {
                        unset($updateData[$field]);
                    }
                }

                return $updateData;
            }
        );
    }

    /**
     * 统一日志记录方法（含表名）
     *
     * @param string $table 操作的表名
     * @param int $recordId 记录ID
     * @param string $operation 操作名称
     * @param string $detail 详细描述
     */
    private static function log(string $table, int $recordId, string $operation, string $detail)
    {
        $message = sprintf(
            "[表名: %s] [记录ID: %d] [操作: %s] %s",
            $table,
            $recordId,
            $operation,
            $detail
        );

        kg_file_put_contents($table, [$message]);
    }

    private function examples()
    {
        // 1. 乐观锁
        // try {
        //     $userModel = new User();
        //     $result = DbLockUtil::optimisticLock($userModel, 1, function($model) {
        //         return [
        //             'balance' => $model->balance - 100,
        //             'update_time' => time()
        //         ];
        //     });
        //     return success($result);
        // } catch (\Exception $e) {
        //     return fail($e->getMessage());
        // }

        // 2. 悲观锁
        // try {
        //     $userModel = new User();
        //     $result = DbLockUtil::pessimisticLock($userModel, 1, function($model) {
        //         return [
        //             'balance' => $model->balance - 100,
        //             'update_time' => time()
        //         ];
        //     });
        //     return success($result);
        // } catch (\Exception $e) {
        //     return fail($e->getMessage());
        // }

        // 3. 混合锁
        // try {
        //     $userModel = new User();
        //     $result = DbLockUtil::hybridLock($userModel, 1, function($model) {
        //         return [
        //             'balance' => $model->balance - 100,
        //             'update_time' => time()
        //         ];
        //     });
        //     return success($result);
        // } catch (\Exception $e) {
        //     return fail($e->getMessage());
        // }

        // 4. 自定义配置
        // DbLockUtil::config([
        //     'optimistic_retry' => 5,
        //     'pessimistic_timeout' => 10
        // ]);
    }
}